#include<iostream>
#include"socket.hpp"
#include <sys/select.h>
#include <sys/time.h>

const int fd_num_max=1024;
const uint16_t default_fd = -1;//将所有需要管理的文件描述符放入一个数组，-1是数组中的无效元素

class SelectServer
{
public:
    //构造函数
	SelectServer(uint16_t port=default_fd)
		:_port(port)
	{
		//初始化数组
		for(int i=0;i<fd_num_max;i++)
		{
			fd_array[i]=default_fd;
		}
	}

	//初始化
	void Init()
	{
		_listensock.Socket();
		_listensock.Bind(_port);
		_listensock.Listen();
	}
 
	//处理客户端发来的链接请求
	void Accepter()
	{
		//客户端发来请求，建立链接
		string clienttip;
		uint16_t clientport=0;
		int sock=_listensock.Accept(clienttip,clientport);
		if(sock<0)
		{
			cout<<"Accepter false"<<endl;
			return;
		}

		cout<<"Accepter success,sock: "<<sock<<endl;
		//由于fd_array[0]存储了监听文件描述符，即从1开始
		int pos=1;
		for(;pos<fd_num_max;pos++)
		{
			//找到数组中没有，放入文件描述符的位置
			if(fd_array[pos]!=default_fd)
				continue;
			else
				break;
		}
		//数组中文件描述符存满了
		if(pos==fd_num_max)
		{
			cout<<"server is full"<<endl;
			close(sock);
		}
		else
		{
			//将客户端建立链接的位置放入数组中，以便下次存入位图中，让select监听该位置
			fd_array[pos]=sock;
			//打印数组中的文件描述符
			PrintFd();
		}
		
	}

	//处理客户端发来的信息
	void Recver(int fd,int pos)
	{
		//用来存储客户端发来的数据
		char buffer[1024];
		ssize_t n=read(fd,buffer,sizeof(buffer)-1);//最后一个换行符不要
		if(n>0)
		{
			buffer[n]=0;
			cout<<"get a messge: "<<buffer<<endl;
		}
		else if(n==0)
		{
			cout<<"client quit,me too"<<endl;
			close(fd);
			//由于客户端退出了，即该文件描述符不需要被select监听，即将其关闭
			fd_array[pos]=default_fd;
		}
		else
		{
			cout<<"read false"<<endl;
			close(fd);
			fd_array[pos]=default_fd;
		}
	}

	//处理已经就绪的文件描述符
	void Hander(fd_set& rfds)
	{
		for(int i=0;i<fd_num_max;i++)
		{
			int fd=fd_array[i];
			//相等，代表该位置没有文件描述符，即跳过
			if(fd==default_fd) continue;

			//判断该文件描述符，有没有被设置进位图中
			if(FD_ISSET(fd,&rfds))
			{
				//客户端要与服务器建立连接
				if(fd==_listensock.Getlistensockfd())
				{
					Accepter();
				}
				else//代表该文件描述符，已经发来了信息，需要我们去读
				{
					Recver(fd,i);
				}

			}
		}
	}

	//启动服务器
	void Start()
	{
		int listensock=_listensock.Getlistensockfd();//保存监听套接字
		fd_array[0]=listensock;////将listen套接字放在第一个，因为在程序运行的全过程中都不会被修改
	
		while(true)
		{
			fd_set rfds;//创建位图
			FD_ZERO(&rfds);//将位图初始化

			int maxfd=fd_array[0];//由于select的第一个参数，需要最大文件描述符+1
			for(int i=0;i<fd_num_max;i++)
			{
				//如果这个位置等于-1，则说明该位置没有文件描述符，即该位置不需要被select监视
				if(fd_array[i]==default_fd) continue;
				//走到这里，说明该位置需要被监视，即将在位图中的该位置设置为1，代表该位置需要被监视
				FD_SET(fd_array[i],&rfds);
				//找到最大的文件描述符
				if(maxfd<fd_array[i])
				{
					maxfd=fd_array[i];
					cout<<"maxfd update"<<endl;
				}
				
				//第一个参数代表秒，第二个参数代表毫秒
				//struct timeval timeout={5,0};代表每过多少时间，select就会读取一次，不会阻塞等待

				//返回已经就绪的个数
				int n=select(maxfd+1,&rfds,nullptr,nullptr,nullptr/*&timout*/);//非阻塞等待
				switch(n)
				{
					case 0:
						cout<<"time out"<<endl;
						break;
					case -1:
						cout<<"select false"<<endl;
						break;
					default:
						cout<<"get a new link!!"<<endl;
						Hander(rfds);//去处理已经就绪了的文件描述符
						break;
				}
			}
		}
	}

	//打印
	void PrintFd()
	{
		cout<<"online fd list: ";
		for(int i=0;i<fd_num_max;i++)
		{
			if(fd_array[i]==default_fd)
				continue;
			cout<<fd_array[i]<<" ";
		}
		cout<<endl;
	}

	//析构函数
	~SelectServer()
	{
		_listensock.Close();
	}

private:
	uint16_t _port;           //端口号
	Sock _listensock;         //套接字
	int fd_array[fd_num_max]; //用于储存所有需要管理的文件描述符的数组
};
